﻿// -----------------------------------------------------------------------
// <copyright file="Visitor.cs" company="none">
// Copyright (c) 2012 M. Alberti, xanatos(at)live.it
// Based on the articles found at 
// http://blogs.msdn.com/b/mitsu/archive/2010/03/02/c-4-expressions-blocks-part-i.aspx, 
// http://blogs.msdn.com/b/mitsu/archive/2010/03/02/c-4-expressions-variables-part-ii.aspx, and
// http://blogs.msdn.com/b/mitsu/archive/2010/03/02/c-4-expressions-loops-goto-label-if-and-even-for-part-iii.aspx.
// Distributed under the MIT/X11 software license, see the accompanying
// file COPYING or http://www.opensource.org/licenses/mit-license.php.
// </copyright>
// -----------------------------------------------------------------------

namespace FluentStatement
{
    using System;
    using System.Collections.Generic;
    using System.Diagnostics.CodeAnalysis;
    using System.Linq;
    using System.Linq.Expressions;
    using System.Reflection;
    using Utilities;

    /// <summary>
    /// 
    /// </summary>
    [SuppressMessage("StyleCop.CSharp.ReadabilityRules", "SA1118:ParameterMustNotSpanMultipleLines", Justification = "More readable this way")]
    public partial class Visitor : ExpressionVisitor
    {
        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Start = typeof(Statement).GetMethods().Where(p => p.Name == "Start" && !p.IsGenericMethodDefinition).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo StartT = typeof(Statement).GetMethods().Where(p => p.Name == "Start" && p.IsGenericMethodDefinition).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Empty = typeof(Statement).GetMethod("Empty");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Do = typeof(Statement).GetMethod("Do");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Return = typeof(Statement).GetMethods().Where(p => p.Name == "Return" && !p.IsGenericMethodDefinition).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo ReturnT = typeof(Statement).GetMethods().Where(p => p.Name == "Return" && p.IsGenericMethodDefinition).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Invoke = typeof(Statement).GetMethods().Where(p => p.Name == "Invoke" && !p.IsGenericMethodDefinition).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo InvokeT = typeof(Statement).GetMethods().Where(p => p.Name == "Invoke" && p.IsGenericMethodDefinition).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Block = typeof(Statement).GetMethods().Where(p => p.Name == "Block" && !p.IsGenericMethodDefinition).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo BlockT = typeof(Statement).GetMethods().Where(p => p.Name == "Block" && p.IsGenericMethodDefinition).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo AssignT = typeof(Statement).GetMethod("Assign");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo PreIncrementT = typeof(Statement).GetMethod("PreIncrement");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo PreDecrementT = typeof(Statement).GetMethod("PreDecrement");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo PostIncrementT = typeof(Statement).GetMethod("PostIncrement");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo PostDecrementT = typeof(Statement).GetMethod("PostDecrement");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo IncreaseT = typeof(Statement).GetMethod("Increase");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo DecreaseT = typeof(Statement).GetMethod("Decrease");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo ValueT = typeof(Statement).GetMethod("Value");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo DefaultValueT = typeof(Statement).GetMethod("DefaultValue");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Label = typeof(Statement).GetMethod("Label");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Goto = typeof(Statement).GetMethod("Goto");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo While = typeof(Statement).GetMethod("While");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo DoWhile = typeof(Statement).GetMethod("DoWhile");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo DoUntil = typeof(Statement).GetMethod("DoUntil");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo ForT = typeof(Statement).GetMethod("For");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo ForEachT = typeof(Statement).GetMethods().Where(p => p.Name == "ForEach" && p.GetGenericArguments().Length == 1).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo ForEachTU = typeof(Statement).GetMethods().Where(p => p.Name == "ForEach" && p.GetGenericArguments().Length == 2).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Break = typeof(Statement).GetMethod("Break");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Continue = typeof(Statement).GetMethod("Continue");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo If = typeof(Statement).GetMethod("If", new[] { typeof(bool), typeof(Statement) });

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo IfElse = typeof(Statement).GetMethod("If", new[] { typeof(bool), typeof(Statement), typeof(Statement) });

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo MakeIf = typeof(Statement).GetMethod("MakeIf");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo IfElseIf = typeof(Statement).GetMethod("If", new[] { typeof(Statement), typeof(Tuple<bool, Statement>[]) });

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo MakeCaseSingleT = typeof(Statement).GetMethods().Where(p => p.Name == "MakeCase" && !p.GetParameters()[0].ParameterType.IsArray).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo MakeCaseArrayT = typeof(Statement).GetMethods().Where(p => p.Name == "MakeCase" && p.GetParameters()[0].ParameterType.IsArray).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo SwitchT = typeof(Statement).GetMethods().Where(p => p.Name == "Switch" && p.IsGenericMethodDefinition && p.GetParameters().Length == 2).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo SwitchDefaultT = typeof(Statement).GetMethods().Where(p => p.Name == "Switch" && p.IsGenericMethodDefinition && p.GetParameters().Length == 3).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Lock = typeof(Statement).GetMethod("Lock");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo TryLockInt32 = typeof(Statement).GetMethod("TryLock", new[] { typeof(object), typeof(int), typeof(Func<bool, Statement>) });

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo TryLockTimeSpan = typeof(Statement).GetMethod("TryLock", new[] { typeof(object), typeof(TimeSpan), typeof(Func<bool, Statement>) });

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo UsingT = typeof(Statement).GetMethod("Using");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Throw = typeof(Statement).GetMethod("Throw");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo Rethrow = typeof(Statement).GetMethod("Rethrow");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo MakeCatch = typeof(Statement).GetMethod("MakeCatch", new[] { typeof(Type), typeof(Func<object, Statement>) });

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo MakeCatchT = typeof(Statement).GetMethods().Where(p => p.Name == "MakeCatch" && p.IsGenericMethodDefinition).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo TryCatch = typeof(Statement).GetMethod("TryCatch");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo TryFinally = typeof(Statement).GetMethod("TryFinally");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo TryCatchFinally = typeof(Statement).GetMethod("TryCatchFinally");

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo TupleCreate = typeof(Tuple).GetMethods().Where(p => p.Name == "Create" && p.ContainsGenericParameters && p.GetGenericArguments().Length == 2).Single();

        /// <summary>
        /// 
        /// </summary>
        protected static readonly MethodInfo TupleBoolActionCreate = TupleCreate.MakeGenericMethod(typeof(bool), typeof(Statement));

        /// <summary>
        /// 
        /// </summary>
        protected static readonly Dictionary<MethodInfo, Func<Visitor, MethodCallExpression, Type, MethodInfo, Expression>> Methods = new Dictionary<MethodInfo, Func<Visitor, MethodCallExpression, Type, MethodInfo, Expression>>
        {
            { Start, (p, q, r, s) => p.StartStatement(q, r, s) },
            { StartT, (p, q, r, s) => p.StartStatement(q, r, s) },
            { Empty, (p, q, r, s) => p.EmptyStatement(q, r, s) },
            { Do, (p, q, r, s) => p.DoStatement(q, r, s) },
            { Return, (p, q, r, s) => p.ReturnStatement(q, r, s) },
            { ReturnT, (p, q, r, s) => p.ReturnTStatement(q, r, s) },
            { Invoke, (p, q, r, s) => p.InvokeStatement(q, r, s) },
            { InvokeT, (p, q, r, s) => p.InvokeStatement(q, r, s) },
            { Block, (p, q, r, s) => p.BlockStatement(q, r, s) },
            { BlockT, (p, q, r, s) => p.BlockTStatement(q, r, s) },
            { AssignT, (p, q, r, s) => p.AssignTStatement(q, r, s) },
            { PreIncrementT, (p, q, r, s) => p.PreIncrementTStatement(q, r, s) },
            { PreDecrementT, (p, q, r, s) => p.PreDecrementTStatement(q, r, s) },
            { PostIncrementT, (p, q, r, s) => p.PostIncrementTStatement(q, r, s) },
            { PostDecrementT, (p, q, r, s) => p.PostDecrementTStatement(q, r, s) },
            { IncreaseT, (p, q, r, s) => p.IncreaseTStatement(q, r, s) },
            { DecreaseT, (p, q, r, s) => p.DecreaseTStatement(q, r, s) },
            { ValueT, (p, q, r, s) => p.ValueTDefaultValueTStatement(q, r, s) },
            { DefaultValueT, (p, q, r, s) => p.ValueTDefaultValueTStatement(q, r, s) },
            { Label, (p, q, r, s) => p.LabelStatement(q, r, s) },
            { Goto, (p, q, r, s) => p.GotoStatement(q, r, s) },
            { While, (p, q, r, s) => p.WhileStatement(q, r, s) },
            { DoWhile, (p, q, r, s) => p.DoWhileStatement(q, r, s) },
            { DoUntil, (p, q, r, s) => p.DoUntilStatement(q, r, s) },
            { ForT, (p, q, r, s) => p.ForTStatement(q, r, s) },
            { ForEachT, (p, q, r, s) => p.ForEachTStatement(q, r, s) },
            { ForEachTU, (p, q, r, s) => p.ForEachTStatement(q, r, s) },
            { Break, (p, q, r, s) => p.BreakStatement(q, r, s) },
            { Continue, (p, q, r, s) => p.ContinueStatement(q, r, s) },
            { If, (p, q, r, s) => p.IfElseStatement(q, r, s) },
            { IfElse, (p, q, r, s) => p.IfElseStatement(q, r, s) },
            { IfElseIf, (p, q, r, s) => p.IfElseIfStatement(q, r, s) },
            { SwitchT, (p, q, r, s) => p.SwitchDefaultTStatement(q, r, s) },
            { SwitchDefaultT, (p, q, r, s) => p.SwitchDefaultTStatement(q, r, s) },
            { Lock, (p, q, r, s) => p.LockStatement(q, r, s) },
            { TryLockInt32, (p, q, r, s) => p.TryLockStatement(q, r, s) },
            { TryLockTimeSpan, (p, q, r, s) => p.TryLockStatement(q, r, s) },
            { UsingT, (p, q, r, s) => p.UsingTStatement(q, r, s) },
            { Throw, (p, q, r, s) => p.ThrowStatement(q, r, s) },
            { Rethrow, (p, q, r, s) => p.RethrowStatement(q, r, s) },
            { TryCatch, (p, q, r, s) => p.TryCatchFinallyStatement(q, r, s) },
            { TryFinally, (p, q, r, s) => p.TryCatchFinallyStatement(q, r, s) },
            { TryCatchFinally, (p, q, r, s) => p.TryCatchFinallyStatement(q, r, s) },
        };

        /// <summary>
        /// 
        /// </summary>
        protected readonly Dictionary<ParameterExpression, Tuple<Dictionary<MemberInfo, ParameterExpression>, ParameterExpression>> Locals = new Dictionary<ParameterExpression, Tuple<Dictionary<MemberInfo, ParameterExpression>, ParameterExpression>>();

        /// <summary>
        /// 
        /// </summary>
        protected readonly Queue<Tuple<LabelTarget, LabelTarget>> Breaks = new Queue<Tuple<LabelTarget, LabelTarget>>();

        /// <summary>
        /// 
        /// </summary>
        protected readonly Dictionary<string, LabelTarget> Labels = new Dictionary<string, LabelTarget>();

        /// <summary>
        /// 
        /// </summary>
        protected readonly Dictionary<string, LabelTarget> MissingLabels = new Dictionary<string, LabelTarget>();

        /// <summary>
        /// 
        /// </summary>
        protected LabelTarget ReturnLabel { get; set; }

        /// <summary>
        /// 
        /// </summary>
        protected bool ReturnLabelUsed { get; set; }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="returnType"></param>
        /// <returns></returns>
        public virtual Expression StartVisit(Expression expression, Type returnType = null)
        {
            this.ReturnLabel = Expression.Label(returnType ?? typeof(void));

            Expression expression2 = this.Visit(expression);

            if (this.ReturnLabelUsed)
            {
                expression2 = this.AddEndLabel(expression2, returnType);
            }

            return expression2;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected static Expression MakeVoid(Expression expression)
        {
            if (expression == null || expression.Type == typeof(void))
            {
                return expression;
            }

            Expression expression2 = Expression.Block(typeof(void), expression);
            return expression2;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected virtual bool ProcessMethodCall(Type declaringType, MethodInfo method)
        {
            return declaringType == typeof(Statement);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <param name="declaringType"></param>
        /// <param name="method"></param>
        /// <returns></returns>
        protected virtual Expression VisitStatementMethod(MethodCallExpression node, Type declaringType, MethodInfo method)
        {
            Func<Visitor, MethodCallExpression, Type, MethodInfo, Expression> method2;

            if (!Methods.TryGetValue(method, out method2))
            {
                throw new NotSupportedException();
            }

            Expression expression = method2(this, node, declaringType, method);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitMethodCall(MethodCallExpression node)
        {
            MethodInfo method = node.Method;
            Type declaringType = method.DeclaringType;

            method = method.IsGenericMethod ? method.GetGenericMethodDefinition() : method;

            if (this.ProcessMethodCall(declaringType, method))
            {
                var expressions = new Stack<Expression>();

                do
                {
                    expressions.Push(this.VisitStatementMethod(node, declaringType, method));

                    if (method.DeclaringType != typeof(Statement) && method.IsExtensionMethodOf(typeof(Statement)))
                    {
                        node = node.Arguments[0] as MethodCallExpression;
                    }
                    else
                    {
                        node = node.Object as MethodCallExpression;
                    }

                    if (node == null)
                    {
                        break;
                    }

                    method = node.Method;
                    declaringType = method.DeclaringType;
                    method = method.IsGenericMethod ? method.GetGenericMethodDefinition() : method;
                }
                while (true);

                if (expressions.Count == 1)
                {
                    return expressions.Pop();
                }

                return Expression.Block(expressions);
            }

            Expression expression = base.VisitMethodCall(node);
            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitParameter(ParameterExpression node)
        {
            Tuple<Dictionary<MemberInfo, ParameterExpression>, ParameterExpression> dict;

            if (this.Locals.TryGetValue(node, out dict))
            {
                if (dict.Item1 != null)
                {
                    throw new NotSupportedException(string.Format("Can't use directly the container '{0}'", node.Name));
                }

                return dict.Item2;
            }

            return base.VisitParameter(node);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="node"></param>
        /// <returns></returns>
        protected override Expression VisitMember(MemberExpression node)
        {
            var param = node.Expression as ParameterExpression;

            return this.GetVariable(node, param) ?? base.VisitMember(node);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <param name="returnType"></param>
        /// <returns></returns>
        protected Expression AddEndLabel(Expression expression, Type returnType)
        {
            return Expression.Block(expression, Expression.Label(this.ReturnLabel, Expression.Default(returnType)));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected T ExpressionOfType<T>(Expression expression) where T : Expression
        {
            if (expression == null)
            {
                return null;
            }

            var constant = expression as ConstantExpression;

            if (constant != null && constant.Value == null)
            {
                return null;
            }

            return (T)expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected Expression VisitBodyLambda(Expression expression)
        {
            return this.VisitBodyLambda(this.ExpressionOfType<LambdaExpression>(expression));
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected Expression VisitBodyLambda(LambdaExpression expression)
        {
            return expression != null ? this.Visit(expression.Body) : null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expression"></param>
        /// <returns></returns>
        protected Expression FixIndexers(Expression expression)
        {
            if (expression.NodeType == ExpressionType.ArrayIndex)
            {
                BinaryExpression expression2 = expression as BinaryExpression;

                if (expression2 != null)
                {
                    expression = Expression.ArrayAccess(expression2.Left, expression2.Right);
                }
            }
            else if (expression.NodeType == ExpressionType.Call)
            {
                // Fix for Index accessors
                MethodCallExpression expression2 = expression as MethodCallExpression;

                if (expression2 != null)
                {
                    if (expression2.Object.Type.IsArray)
                    {
                        // We have the Get method used for multidimensional arrays.
                        if (expression2.Method.Name == "Get")
                        {
                            expression = Expression.ArrayAccess(expression2.Object, expression2.Arguments);
                        }
                    }
                    else if (expression2.Method.IsSpecialName && expression2.Method.Name.StartsWith("get_"))
                    {
                        // We have a getter. We have to find the property "connected" to the getter.
                        // getters always start with "get_" and are IsSpecialName.
                        // We use the parameterTypes to find the exact overload. Fortunately the
                        // left2.Method.DeclaringType will return the interface for explicitly declared properties/indexers.
                        var parameterTypes = (Type[])Array.ConvertAll(expression2.Method.GetParameters(), p => p.ParameterType);
                        PropertyInfo property = expression2.Method.DeclaringType.GetProperty(expression2.Method.Name.Substring(4), BindingFlags.Instance | BindingFlags.Static | BindingFlags.Public | BindingFlags.NonPublic, null, expression2.Method.ReturnType, parameterTypes, null);

                        if (property == null)
                        {
                            throw new NotSupportedException();
                        }

                        MethodInfo getter = property.GetGetMethod(true);

                        if (getter != expression2.Method)
                        {
                            throw new NotSupportedException();
                        }

                        // Only if it's an indexer
                        if (property.GetIndexParameters().Length != 0)
                        {
                            expression = Expression.MakeIndex(expression2.Object, property, expression2.Arguments);
                        }
                    }
                }
            }

            return expression;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="expressions"></param>
        /// <returns></returns>
        protected Type FindCommonType(IEnumerable<Expression> expressions)
        {
            Type type = null;

            foreach (Expression expression in expressions)
            {
                if (expression.Type == typeof(void))
                {
                    return typeof(void);
                }

                if (type == null)
                {
                    type = expression.Type;
                }
                else if (type.IsValueType)
                {
                    // For value types only direct equality is good
                    if (type != expression.Type)
                    {
                        return typeof(void);
                    }
                }
                else if (type != expression.Type && !type.IsAssignableFrom(expression.Type))
                {
                    if (expression.Type.IsAssignableFrom(type))
                    {
                        type = expression.Type;
                    }
                    else
                    {
                        return typeof(void);
                    }
                }
            }

            return type ?? typeof(void);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="member"></param>
        /// <param name="param"></param>
        /// <returns></returns>
        protected ParameterExpression GetVariable(MemberExpression member, ParameterExpression param)
        {
            if (member != null && param != null)
            {
                Tuple<Dictionary<MemberInfo, ParameterExpression>, ParameterExpression> dict;

                if (this.Locals.TryGetValue(param, out dict))
                {
                    ParameterExpression param2;

                    if (dict.Item1 != null)
                    {
                        if (dict.Item1.TryGetValue(member.Member, out param2))
                        {
                            return param2;
                        }

                        throw new NotSupportedException(string.Format("Missing variable '{1}' from container '{0}'", param.Name, member.Member.Name));
                    }
                }
            }

            return null;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="locals"></param>
        /// <param name="params"></param>
        /// <param name="blockVariables"></param>
        /// <param name="block"></param>
        /// <returns></returns>
        protected ParameterExpression[] LoadLocals(Expression locals, ParameterExpression[] @params, List<ParameterExpression> blockVariables, List<Expression> block)
        {
            if (locals == null)
            {
                return null;
            }

            if (locals is NewExpression && locals.Type.IsAnonymousType())
            {
                NewExpression locals2 = this.ExpressionOfType<NewExpression>(locals);
                this.LoadLocalsImpl(locals2, @params, blockVariables, block);
            }
            else
            {
                this.LoadLocalImpl(locals, @params, blockVariables, block);
            }

            return @params;
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="local"></param>
        /// <param name="params"></param>
        /// <param name="blockVariables"></param>
        /// <param name="block"></param>
        protected void LoadLocalImpl(Expression local, ParameterExpression[] @params, List<ParameterExpression> blockVariables, List<Expression> block)
        {
            ParameterExpression param = Expression.Variable(local.Type);
            BinaryExpression binary = Expression.Assign(param, this.Visit(local));

            var dict = Tuple.Create((Dictionary<MemberInfo, ParameterExpression>)null, param);

            for (int i = 0; i < @params.Length; i++)
            {
                this.Locals.Add(@params[i], dict);
            }

            blockVariables.Add(param);
            block.Add(binary);
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="locals"></param>
        /// <param name="params"></param>
        /// <param name="blockVariables"></param>
        /// <param name="block"></param>
        protected void LoadLocalsImpl(NewExpression locals, ParameterExpression[] @params, List<ParameterExpression> blockVariables, List<Expression> block)
        {
            var dict = Tuple.Create(new Dictionary<MemberInfo, ParameterExpression>(), (ParameterExpression)null);

            for (int i = 0; i < @params.Length; i++)
            {
                this.Locals.Add(@params[i], dict);
            }

            for (int i = 0; i < locals.Members.Count; i++)
            {
                var pi = (PropertyInfo)locals.Members[i];

                ParameterExpression param = Expression.Variable(pi.PropertyType, pi.Name);
                BinaryExpression binary = Expression.Assign(param, this.Visit(locals.Arguments[i]));

                blockVariables.Add(param);
                block.Add(binary);

                dict.Item1.Add(pi, param);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="params"></param>
        protected void UnloadLocals(ParameterExpression[] @params)
        {
            for (int i = 0; i < @params.Length; i++)
            {
                this.Locals.Remove(@params[i]);
            }
        }
    }
}
